Estimating The Forward Electricity Curve In Brazil With A Model Of Two Agents Using Contracts By Difference And ECP_G Function

Authors: Felipe Van de Sande Araujo, Cristina Spineti Luz, Leonardo Lima Gomes, Luís Eduardo Teixeira Brandão

Abstract: The development of simple and effective mechanisms to estimate the value of the forward curve of power could enable market participants to better price hedging or speculative positions. This could in turn provide transparency in future price definition to all market participants and lead to more safety and liquidity in the market for electricity futures and power derivatives. This work presents a model for two market participants, a buyer and a seller of a contract for difference on the future spot price of electricity in southwest Brazil. It is shown that this model is representative of all market participants that have exposure to the future price of power. Each participant’s utility function is modeled using a Generalized Extended CVaR Preference (ECP_G) and the market equilibrium is obtained through the minimization of the quadratic difference between the certainty equivalent of both agents. The results are compared with prediction of the future spot price of power made by market specialists and found to yield reasonable results when using out of sample data.

This work presents the calculations done for the article referenced above. The following calculations show the sensitivity analysis for the optimized parameters which were obtained in the optimization step (link) and the risk-free return rate used in the validation step (link). All the code was run in RStudio using the version of the software below.

Software version

R.version
               _                           
platform       x86_64-w64-mingw32          
arch           x86_64                      
os             mingw32                     
system         x86_64, mingw32             
status                                     
major          4                           
minor          0.1                         
year           2020                        
month          06                          
day            06                          
svn rev        78648                       
language       R                           
version.string R version 4.0.1 (2020-06-06)
nickname       See Things Now              

Setting of the environment

# Set plot font
windowsFonts(A = windowsFont("Times New Roman")) 

Local functions definition

The first function is the determination of the ECPG phi which is the price which, when added to the contract price, provides the certainty equivalent for the agents. This is clarified in the text.

ECPG.phi <- function(parameters, dataset, auxList, buyer=TRUE) {
  if (any(parameters < 0) || any(parameters > 1)) return(NA)
  sumLambda = parameters['Lambda1'] + parameters['Lambda2']
  if (sumLambda > 1) return(NA)
  Lambda0 = 1 - sumLambda
  if (Lambda0 < 0) return(NA)
  
  if (buyer) {
    mean.pld = auxList$meanPLD
    VaR0 = auxList$VaR0
    VaR1 = auxList$VaR1
  } else {
    dataset = -dataset
    mean.pld = -auxList$meanPLD
    VaR0 = auxList$VaR1
    VaR1 = auxList$VaR0
  }
  
  VaRAlpha1 = apply(dataset, 1, quantile, (1-parameters['Alpha1']))
  VaRAlpha2 = apply(dataset, 1, quantile, (1-parameters['Alpha2']))
  
  CVaR1 = CVaR2 = NA
  
  CVaR1.data = dataset
  CVaR1.data[dataset > VaRAlpha1] <- NA
  
  CVaR2.data = dataset
  CVaR2.data[dataset > VaRAlpha2] <- NA
  
  CVaR1 = apply(CVaR1.data, 1, mean, na.rm=TRUE)
  CVaR2 = apply(CVaR2.data, 1, mean, na.rm=TRUE)
  
  Sum1 = parameters['Lambda1']*VaRAlpha1 + parameters['Lambda2']*VaRAlpha2
  Sum2 = parameters['Lambda1']*(CVaR1 - VaRAlpha1) + parameters['Lambda2']*(CVaR2 - VaRAlpha2)
  
  Limit1 = Lambda0*VaR0 + Sum1
  Limit2 = Lambda0*VaRAlpha1 + Sum1
  Limit3 = Lambda0*VaRAlpha2 + Sum1
  Limit4 = Lambda0*VaR1 + Sum1
  
  ECPG = Lambda0*mean.pld + parameters['Lambda1']*CVaR1 + parameters['Lambda2']*CVaR2
  
  nIter = nrow(dataset)
  Lambda1 = rep(parameters['Lambda1'], nIter)
  Lambda2 = rep(parameters['Lambda2'], nIter)
  
  if (any(ECPG>=Limit2)) Lambda1[ECPG>=Limit2] = 0
  if (any(ECPG>=Limit3)) Lambda2[ECPG>=Limit3] = 0
  
  Q = Lambda0 + Lambda1/(1-parameters['Alpha1']) + Lambda2/(1-parameters['Alpha2'])
  
  A = Sum2 + Lambda0*mean.pld + 
    Lambda1*VaRAlpha1/(1-parameters['Alpha1']) +
    Lambda2*VaRAlpha2/(1-parameters['Alpha2'])
  
  Eq = A/Q
  
  if (any(is.na(Eq))) return(NA)
  return(Eq)
}

We will use a list with all the parameters and data which will be called ECPG_Object, in order to facilitate the next function calls. The next function will create the list.

createECPGObject <- function(dataset, discountVector, forwardPrice, buyerParam, sellerParam) {
  VaR0 = apply(dataset, 1, quantile, 1)
  VaR1 = apply(dataset, 1, quantile, 0)
  meanPLD = apply(dataset, 1, mean)
  
  ECPGObject = list(
    dataset=dataset,
    discountVector=discountVector,
    forwardPrice=forwardPrice,
    buyerParam=buyerParam,
    sellerParam=sellerParam,
    VaR0=VaR0,
    VaR1=VaR1,
    meanPLD=meanPLD
  )
  return(ECPGObject)
}

Then we will define a function to obtain the equilibrium price using the calculated phi for each agent. The distinction between the agents must be made because the signal of the price data changes between them, because of the Contract for Differences revenue equations.

ECPG.equilibrium <- function (ECPGobject) {
  auxList = list(
    VaR0 = ECPGobject$VaR0,
    VaR1 = ECPGobject$VaR1,
    meanPLD = ECPGobject$meanPLD
  )
  
  buyer.phi = ECPG.phi(ECPGobject$buyerParam, ECPGobject$dataset, auxList)
  seller.phi = -ECPG.phi(ECPGobject$sellerParam, ECPGobject$dataset, auxList, buyer=FALSE)
  
  equilibrium.prices = (buyer.phi + seller.phi)/2
  
  adjusted.prices = equilibrium.prices/ECPGobject$discount
  
  averagePrice = mean(adjusted.prices)
  
  ECPGobject$buyerPhi = buyer.phi
  ECPGobject$sellerPhi = seller.phi
  ECPGobject$equilibriumPrices = equilibrium.prices
  ECPGobject$adjustedPrices=adjusted.prices
  ECPGobject$averagePrice=averagePrice
  
  return(ECPGobject)
}

Next function is used in a recursive call to calculate sensitivity.

sensitivity.foo <- function(i, arg, output, combination.matrix, ECPGobject) {
  
  ECPGobject[[arg]][TRUE] = combination.matrix[i, ]
  
  return(ECPG.equilibrium(ECPGobject)[[output]])
}

And finally a plotting function for the ECPG_Object.

sensitivity.prettyPlot <- function(plot.matrix, xlabel="", ylim=c(0,250), lwd = 2, legendTitle, legendPos="bottomright", inset=0.05, ncol=1, cex=1, legCex=0.9) {
  Nseries = ncol(plot.matrix)
  viridis.pallette = viridis(Nseries)
  line.types = rep(1, Nseries)
  
  Nrows = 1:nrow(plot.matrix)
  labels=row.names(plot.matrix)
  
  par(family="A", cex=cex)
  matplot(Nrows, plot.matrix, type='l', xlab=xlabel, ylab='Energy Price (R$/MWh)', ylim=ylim, lty=line.types, col=viridis.pallette, lwd=lwd, axes=F)
  axis(2)
  axis(side=1,at=Nrows,labels=labels)
  grid (NULL,NULL, lty = 6, col = "grey")
  legend(x=legendPos, inset=inset, legend=colnames(plot.matrix), horiz=FALSE, lty=line.types, col=viridis.pallette, lwd=lwd, cex=legCex, ncol=ncol, title=legendTitle)
}

Local variables definition

These variables will be used for all sensitivity tests. The optimization parameters are obtained from the training step.

 
buyer.param = c(Lambda1 = 0.5712411, Lambda2 = 0.1400453, Alpha1 = 0.5845306, Alpha2 = 0.5995087)
seller.param = c(Lambda1 = 0.3483552, Lambda2 = 0.5599081, Alpha1 = 0.7256726, Alpha2 = 0.9737610)

Variables for January A+1 analysis

Create a vector with discount rates to adjust the equilibrium prices with the risk-free return rate. Risk-free return rate is obtained as an approximation of the SELIC national rate.

# Set risk free rates
anualRate = 0.05
monthlyRate = (1+anualRate)^(1/12)-1
temp.discount.vector <- rep(NA, 23)
for (i in 1:23){
  temp.discount.vector[i] <- (1+monthlyRate)^(i-1)
}
januaryA1.discountVector = temp.discount.vector[12:23]

Input the forward price for electricity obtained from DCide Energia.

januaryA1.forwardPrice = 199.32

Loading the January A+1 data

Loading future spot price (PLD) data.

januaryA1.original.data = read.csv(file='./Data/DataJanuaryA1.csv', col.names=paste(month.abb, 2020, sep="-"))
januaryA1.data = t(januaryA1.original.data)

Create a list with all variables and data to pass on to functions.

januaryA1.ECPG_object = createECPGObject(dataset = januaryA1.data, buyerParam = buyer.param, sellerParam = seller.param, discountVector = januaryA1.discountVector, forwardPrice = januaryA1.forwardPrice)

Get the original results for comparison.

januaryA1.Results = ECPG.equilibrium(januaryA1.ECPG_object)

Sensitivity for Lambda 1 and Lambda 2

The first analysis is regarding \(\lambda_1\) and \(\lambda_2\) parameters for each agent. Those are the auxiliary variables set for this task.

lambdas.vec = seq(0, 0.45, by=0.05)
lambdas.matrix = as.matrix(expand.grid(lambdas.vec, lambdas.vec))
colnames(lambdas.matrix) = c('Lambda1', 'Lambda2')
nLambdas = nrow(lambdas.matrix)

From the range set above a combination matrix is created with the buyer parameters.

buyer.lambdas.matrix = cbind(lambdas.matrix, Alpha1=rep(buyer.param['Alpha1'], nLambdas), Alpha2=rep(buyer.param['Alpha2'], nLambdas))

Next, call the sensitivity function and create a matrix of the results for the buyer.

lambdaBuyer.sensitivity = sapply(1:nLambdas,
                                 sensitivity.foo,
                                 arg = "buyerParam",
                                 output = "averagePrice",
                                 combination.matrix = buyer.lambdas.matrix,
                                 januaryA1.ECPG_object)

lambdaBuyer.sensiMatrix = matrix(lambdaBuyer.sensitivity,
                                 ncol=length(lambdas.vec),
                                 byrow = TRUE,
                                 dimnames = list(lambdas.vec, lambdas.vec))

Do the same for the seller.

seller.lambdas.matrix = cbind(lambdas.matrix, Alpha1=rep(seller.param['Alpha1'], nLambdas), Alpha2=rep(seller.param['Alpha2'], nLambdas))
lambdaSeller.sensitivity = sapply(1:nLambdas,
                                  sensitivity.foo,
                                  arg = "sellerParam",
                                  output = "averagePrice",
                                 combination.matrix = seller.lambdas.matrix,
                                 januaryA1.ECPG_object)

lambdaSeller.sensiMatrix = matrix(lambdaSeller.sensitivity,
                                 ncol=length(lambdas.vec),
                                 byrow = TRUE,
                                 dimnames = list(lambdas.vec, lambdas.vec))

The result are plotted in an interactive widget using plotly.

require(plotly)
Loading required package: plotly
Loading required package: ggplot2
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: 㤼㸱plotly㤼㸲

The following object is masked from 㤼㸱package:ggplot2㤼㸲:

    last_plot

The following object is masked from 㤼㸱package:stats㤼㸲:

    filter

The following object is masked from 㤼㸱package:graphics㤼㸲:

    layout
ax.xy <- list(ticketmode = 'array', ticktext = as.character(lambdas.vec), tickvals=0:9)
axz <- list(title = "Power Price (R$/MWh)")

Analysis of \(\lambda_1\) and \(\lambda_2\) for the buyer.

fig.Buyer <- plot_ly(width=700, height=500) %>%
  add_surface(z = lambdaBuyer.sensiMatrix) %>%
  layout(scene = list(xaxis=c(ax.xy, title = "Buyer Lambda1"),yaxis=c(ax.xy, title = "Buyer Lambda2"),zaxis=axz))

fig.Buyer

Analysis of \(\lambda_1\) and \(\lambda_2\) for the seller.

fig.Seller <- plot_ly(width=700, height=500) %>%
  add_surface(z = lambdaSeller.sensiMatrix) %>%
  layout(scene = list(xaxis=c(ax.xy, title = "Seller Lambda1"),yaxis=c(ax.xy, title = "Seller Lambda2"),zaxis=axz))

fig.Seller

Sensitivity for Lambda 1 and Alpha 1 in ECP

The following test is regarding both \(\alpha\) and \(\lambda\) parameters for each agent using ECP measure instead of ECP_G. Those are the auxiliary variables set for this task.

lambdaAlpha.vec = seq(0, 0.9, by=0.1)
lambdaAlpha.matrix = as.matrix(expand.grid(lambdaAlpha.vec, lambdaAlpha.vec))
colnames(lambdaAlpha.matrix) = c('Lambda1', 'Alpha1')
nLambdaAlpha = nrow(lambdaAlpha.matrix)

From the range set above a combination matrix is created with the buyer parameters.

buyer.lambdaAlpha.matrix = cbind(lambdaAlpha.matrix, Lambda2=rep(0, nLambdaAlpha), Alpha2=rep(buyer.param['Alpha2'], nLambdaAlpha))
buyer.lambdaAlpha.matrix = buyer.lambdaAlpha.matrix[, c('Lambda1', 'Lambda2', 'Alpha1', 'Alpha2')]

Next, call the sensitivity function and create a matrix of the results for the buyer.

lambdaAlphaBuyer.sensitivity = sapply(1:nLambdaAlpha,
                                 sensitivity.foo,
                                 arg="buyerParam",
                                 output = "averagePrice",
                                 combination.matrix = buyer.lambdaAlpha.matrix,
                                 within(januaryA1.ECPG_object, buyerParam[TRUE] <- rep(0,4)))

lambdaAlphaBuyer.sensiMatrix = matrix(lambdaAlphaBuyer.sensitivity,
                                 ncol=length(lambdaAlpha.vec),
                                 byrow = TRUE,
                                 dimnames = list(lambdaAlpha.vec, lambdaAlpha.vec))

And the same is done for the seller.

seller.lambdaAlpha.matrix = cbind(lambdaAlpha.matrix, Lambda2=rep(0, nLambdaAlpha), Alpha2=rep(seller.param['Alpha2'], nLambdaAlpha))
seller.lambdaAlpha.matrix = seller.lambdaAlpha.matrix[, c('Lambda1', 'Lambda2', 'Alpha1', 'Alpha2')]
lambdaAlphaSeller.sensitivity = sapply(1:nLambdaAlpha,
                                  sensitivity.foo,arg="sellerParam",
                                  output = "averagePrice",
                                  combination.matrix = seller.lambdaAlpha.matrix,
                                  within(januaryA1.ECPG_object, sellerParam[TRUE] <- rep(0,4)))

lambdaAlphaSeller.sensiMatrix = matrix(lambdaAlphaSeller.sensitivity,
                                  ncol=length(lambdaAlpha.vec),
                                  byrow = TRUE,
                                  dimnames = list(lambdaAlpha.vec, lambdaAlpha.vec))

The results are visualized with our own plot function. Library viridis is called to provide color pallette for colorblind visualization.

require(viridis)
Loading required package: viridis
Loading required package: viridisLite

Alpha-Lambda sensitivity for the buyer.

sensitivity.prettyPlot(lambdaAlphaBuyer.sensiMatrix, xlabel=expression(paste("Buyer ", alpha, 1)), ylim=c(170, 205), ncol=2, cex=1.2, legCex=0.7, lwd=2, legendTitle=expression(paste("Buyer ", lambda, 1)))

Alpha-Lambda sensitivity for the seller.

sensitivity.prettyPlot(lambdaAlphaSeller.sensiMatrix, xlabel=expression(paste("Seller ", alpha, 1)), ylim=c(0, 180), legendTitle=expression(paste("Seller ", lambda, 1)), legendPos = "topleft", ncol=5, inset=0.02, cex=1.2, lwd=2, legCex=0.7)

Sensitivity for the risk-free rate of return in January A+2 results

This section tests the result for both the average price and the adjusted prices obtained by the model with different levels of the risk-free rate.

Define Variables for January A+2 validation

Create a vector with discount rates to adjust the equilibrium prices with the risk-free return rate. At this point all values are set to 1.

januaryA2.discountVector = rep(1, 12)

Input the forward price for electricity obtained from DCide Energia.

januaryA2.forwardPrice = 179.26

Loading the January A+2 data

Loading future spot price (PLD) data.

januaryA2.original.data = read.csv(file='./Data/DataJanuaryA2.csv', col.names=paste(month.abb, 2021, sep="-"))
januaryA2.data = t(januaryA2.original.data)

Create a list with all variables and data to pass on to functions.

januaryA2.ECPG_object = createECPGObject(dataset = januaryA2.data, buyerParam = buyer.param, sellerParam = seller.param, discountVector = januaryA2.discountVector, forwardPrice = januaryA2.forwardPrice)

Prepare variables for the sensitivity analysis

Set auxiliary variables.

annualRates.vec = seq(0, 0.09, by=0.01)
monthlyRates.vec = (1+annualRates.vec)^(1/12)-1
nRates = length(monthlyRates.vec)

Create a discount matrix to be passed to the sensitivity function.

discount.matrix <- matrix(NA, ncol=35, nrow=nRates)
for (ii in 1:nRates) {
  for (i in 1:35){
    discount.matrix[ii, i] <- (1+monthlyRates.vec[ii])^(i-1)
  }
}
discount.matrix = discount.matrix[, 24:35]
rownames(discount.matrix) = annualRates.vec

Apply the sensitivity function to obtain the average price for each risk-free rate.

rates.sensitivity = sapply(1:nRates,
                            sensitivity.foo,
                            arg="discountVector",
                           output = "averagePrice", 
                            combination.matrix = discount.matrix,
                            januaryA2.ECPG_object)
names(rates.sensitivity) = annualRates.vec

Plot the results

plot(annualRates.vec, rates.sensitivity, ylim=c(0,200), ylab="Energy Price (R$/MWh)", xlab="Annual Risk-Free Rate", col="steelblue")

Apply the sensitivity function to obtain the adjusted prices for each risk-free rate and arrange the results in a matrix.

rates.adjustedPrices = sapply(1:nRates,
                           sensitivity.foo,
                           arg="discountVector",
                           output = "adjustedPrices", 
                           combination.matrix = discount.matrix,
                           januaryA2.ECPG_object)

rates.sensitivity.matrix = matrix(rates.adjustedPrices,
                                  ncol = nRates,
                                  byrow = FALSE,
                                  dimnames = list(rownames(januaryA2.ECPG_object$dataset), annualRates.vec))

Plot the results

sensitivity.prettyPlot(rates.sensitivity.matrix, ylim=c(0, 205), ncol=5, cex=1.2, legCex=0.7, lwd=2, legendTitle="Risk-Free Rate aa.")

Check if results are internally consistent.

januaryA2.Results = ECPG.equilibrium(within(januaryA2.ECPG_object, discountVector <- discount.matrix["0.05", ]))
januaryA2.Results$averagePrice == rates.sensitivity["0.05"]
0.05 
TRUE 

See also:

Model Training (link)

Model Validation (link)

LS0tDQp0aXRsZTogIk1vZGVsIFNlbnNpYmlsaXR5Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyMgIEVzdGltYXRpbmcgVGhlIEZvcndhcmQgRWxlY3RyaWNpdHkgQ3VydmUgSW4gQnJhemlsIFdpdGggQSBNb2RlbCBPZiBUd28gQWdlbnRzIFVzaW5nIENvbnRyYWN0cyBCeSBEaWZmZXJlbmNlIEFuZCBFQ1BfRyBGdW5jdGlvbg0KDQpBdXRob3JzOiBGZWxpcGUgVmFuIGRlIFNhbmRlIEFyYXVqbywgQ3Jpc3RpbmEgU3BpbmV0aSBMdXosIExlb25hcmRvIExpbWEgR29tZXMsIEx1w61zIEVkdWFyZG8gVGVpeGVpcmEgQnJhbmTDo28NCg0KQWJzdHJhY3Q6IFRoZSBkZXZlbG9wbWVudCBvZiBzaW1wbGUgYW5kIGVmZmVjdGl2ZSBtZWNoYW5pc21zIHRvIGVzdGltYXRlIHRoZSB2YWx1ZSBvZiB0aGUgZm9yd2FyZCBjdXJ2ZSBvZiBwb3dlciBjb3VsZCBlbmFibGUgbWFya2V0IHBhcnRpY2lwYW50cyB0byBiZXR0ZXIgcHJpY2UgaGVkZ2luZyBvciBzcGVjdWxhdGl2ZSBwb3NpdGlvbnMuIFRoaXMgY291bGQgaW4gdHVybiBwcm92aWRlIHRyYW5zcGFyZW5jeSBpbiBmdXR1cmUgcHJpY2UgZGVmaW5pdGlvbiB0byBhbGwgbWFya2V0IHBhcnRpY2lwYW50cyBhbmQgbGVhZCB0byBtb3JlIHNhZmV0eSBhbmQgbGlxdWlkaXR5IGluIHRoZSBtYXJrZXQgZm9yIGVsZWN0cmljaXR5IGZ1dHVyZXMgYW5kIHBvd2VyIGRlcml2YXRpdmVzLiBUaGlzIHdvcmsgcHJlc2VudHMgYSBtb2RlbCBmb3IgdHdvIG1hcmtldCBwYXJ0aWNpcGFudHMsIGEgYnV5ZXIgYW5kIGEgc2VsbGVyIG9mIGEgY29udHJhY3QgZm9yIGRpZmZlcmVuY2Ugb24gdGhlIGZ1dHVyZSBzcG90IHByaWNlIG9mIGVsZWN0cmljaXR5IGluIHNvdXRod2VzdCBCcmF6aWwuIEl0IGlzIHNob3duIHRoYXQgdGhpcyBtb2RlbCBpcyByZXByZXNlbnRhdGl2ZSBvZiBhbGwgbWFya2V0IHBhcnRpY2lwYW50cyB0aGF0IGhhdmUgZXhwb3N1cmUgdG8gdGhlIGZ1dHVyZSBwcmljZSBvZiBwb3dlci4gRWFjaCBwYXJ0aWNpcGFudOKAmXMgdXRpbGl0eSBmdW5jdGlvbiBpcyBtb2RlbGVkIHVzaW5nIGEgR2VuZXJhbGl6ZWQgRXh0ZW5kZWQgQ1ZhUiBQcmVmZXJlbmNlIChFQ1BfRykgYW5kIHRoZSBtYXJrZXQgZXF1aWxpYnJpdW0gaXMgb2J0YWluZWQgdGhyb3VnaCB0aGUgbWluaW1pemF0aW9uIG9mIHRoZSBxdWFkcmF0aWMgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBjZXJ0YWludHkgZXF1aXZhbGVudCBvZiBib3RoIGFnZW50cy4gVGhlIHJlc3VsdHMgYXJlIGNvbXBhcmVkIHdpdGggcHJlZGljdGlvbiBvZiB0aGUgZnV0dXJlIHNwb3QgcHJpY2Ugb2YgcG93ZXIgbWFkZSBieSBtYXJrZXQgc3BlY2lhbGlzdHMgYW5kIGZvdW5kIHRvIHlpZWxkIHJlYXNvbmFibGUgcmVzdWx0cyB3aGVuIHVzaW5nIG91dCBvZiBzYW1wbGUgZGF0YS4NCg0KVGhpcyB3b3JrIHByZXNlbnRzIHRoZSBjYWxjdWxhdGlvbnMgZG9uZSBmb3IgdGhlIGFydGljbGUgcmVmZXJlbmNlZCBhYm92ZS4gVGhlIGZvbGxvd2luZyBjYWxjdWxhdGlvbnMgc2hvdyB0aGUgc2Vuc2liaWxpdHkgYW5hbHlzaXMgZm9yIHRoZSBvcHRpbWl6ZWQgcGFyYW1ldGVycyB3aGljaCB3ZXJlIG9idGFpbmVkIGluIHRoZSBvcHRpbWl6YXRpb24gc3RlcCAoW2xpbmtdKC4vTW9kZWxUcmFpbmluZ05vdGVib29rLm5iLmh0bWwpKSBhbmQgdGhlIHJpc2stZnJlZSByZXR1cm4gcmF0ZSB1c2VkIGluIHRoZSB2YWxpZGF0aW9uIHN0ZXAgKFtsaW5rXSguL01vZGVsVmFsaWRhdGlvbk5vdGVib29rLm5iLmh0bWwpKS4gQWxsIHRoZSBjb2RlIHdhcyBydW4gaW4gUlN0dWRpbyB1c2luZyB0aGUgdmVyc2lvbiBvZiB0aGUgc29mdHdhcmUgYmVsb3cuDQoNCiMjIyBTb2Z0d2FyZSB2ZXJzaW9uDQoNCmBgYHtyfQ0KUi52ZXJzaW9uDQpgYGANCg0KIyMjIFNldHRpbmcgb2YgdGhlIGVudmlyb25tZW50DQoNCmBgYHtyfQ0KIyBTZXQgcGxvdCBmb250DQp3aW5kb3dzRm9udHMoQSA9IHdpbmRvd3NGb250KCJUaW1lcyBOZXcgUm9tYW4iKSkgDQpgYGANCg0KIyMjIExvY2FsIGZ1bmN0aW9ucyBkZWZpbml0aW9uDQoNClRoZSBmaXJzdCBmdW5jdGlvbiBpcyB0aGUgZGV0ZXJtaW5hdGlvbiBvZiB0aGUgRUNQRyBwaGkgd2hpY2ggaXMgdGhlIHByaWNlIHdoaWNoLCB3aGVuIGFkZGVkIHRvIHRoZSBjb250cmFjdCBwcmljZSwgcHJvdmlkZXMgdGhlIGNlcnRhaW50eSBlcXVpdmFsZW50IGZvciB0aGUgYWdlbnRzLiBUaGlzIGlzIGNsYXJpZmllZCBpbiB0aGUgdGV4dC4NCg0KYGBge3J9DQpFQ1BHLnBoaSA8LSBmdW5jdGlvbihwYXJhbWV0ZXJzLCBkYXRhc2V0LCBhdXhMaXN0LCBidXllcj1UUlVFKSB7DQogIGlmIChhbnkocGFyYW1ldGVycyA8IDApIHx8IGFueShwYXJhbWV0ZXJzID4gMSkpIHJldHVybihOQSkNCiAgc3VtTGFtYmRhID0gcGFyYW1ldGVyc1snTGFtYmRhMSddICsgcGFyYW1ldGVyc1snTGFtYmRhMiddDQogIGlmIChzdW1MYW1iZGEgPiAxKSByZXR1cm4oTkEpDQogIExhbWJkYTAgPSAxIC0gc3VtTGFtYmRhDQogIGlmIChMYW1iZGEwIDwgMCkgcmV0dXJuKE5BKQ0KICANCiAgaWYgKGJ1eWVyKSB7DQogICAgbWVhbi5wbGQgPSBhdXhMaXN0JG1lYW5QTEQNCiAgICBWYVIwID0gYXV4TGlzdCRWYVIwDQogICAgVmFSMSA9IGF1eExpc3QkVmFSMQ0KICB9IGVsc2Ugew0KICAgIGRhdGFzZXQgPSAtZGF0YXNldA0KICAgIG1lYW4ucGxkID0gLWF1eExpc3QkbWVhblBMRA0KICAgIFZhUjAgPSBhdXhMaXN0JFZhUjENCiAgICBWYVIxID0gYXV4TGlzdCRWYVIwDQogIH0NCiAgDQogIFZhUkFscGhhMSA9IGFwcGx5KGRhdGFzZXQsIDEsIHF1YW50aWxlLCAoMS1wYXJhbWV0ZXJzWydBbHBoYTEnXSkpDQogIFZhUkFscGhhMiA9IGFwcGx5KGRhdGFzZXQsIDEsIHF1YW50aWxlLCAoMS1wYXJhbWV0ZXJzWydBbHBoYTInXSkpDQogIA0KICBDVmFSMSA9IENWYVIyID0gTkENCiAgDQogIENWYVIxLmRhdGEgPSBkYXRhc2V0DQogIENWYVIxLmRhdGFbZGF0YXNldCA+IFZhUkFscGhhMV0gPC0gTkENCiAgDQogIENWYVIyLmRhdGEgPSBkYXRhc2V0DQogIENWYVIyLmRhdGFbZGF0YXNldCA+IFZhUkFscGhhMl0gPC0gTkENCiAgDQogIENWYVIxID0gYXBwbHkoQ1ZhUjEuZGF0YSwgMSwgbWVhbiwgbmEucm09VFJVRSkNCiAgQ1ZhUjIgPSBhcHBseShDVmFSMi5kYXRhLCAxLCBtZWFuLCBuYS5ybT1UUlVFKQ0KICANCiAgU3VtMSA9IHBhcmFtZXRlcnNbJ0xhbWJkYTEnXSpWYVJBbHBoYTEgKyBwYXJhbWV0ZXJzWydMYW1iZGEyJ10qVmFSQWxwaGEyDQogIFN1bTIgPSBwYXJhbWV0ZXJzWydMYW1iZGExJ10qKENWYVIxIC0gVmFSQWxwaGExKSArIHBhcmFtZXRlcnNbJ0xhbWJkYTInXSooQ1ZhUjIgLSBWYVJBbHBoYTIpDQogIA0KICBMaW1pdDEgPSBMYW1iZGEwKlZhUjAgKyBTdW0xDQogIExpbWl0MiA9IExhbWJkYTAqVmFSQWxwaGExICsgU3VtMQ0KICBMaW1pdDMgPSBMYW1iZGEwKlZhUkFscGhhMiArIFN1bTENCiAgTGltaXQ0ID0gTGFtYmRhMCpWYVIxICsgU3VtMQ0KICANCiAgRUNQRyA9IExhbWJkYTAqbWVhbi5wbGQgKyBwYXJhbWV0ZXJzWydMYW1iZGExJ10qQ1ZhUjEgKyBwYXJhbWV0ZXJzWydMYW1iZGEyJ10qQ1ZhUjINCiAgDQogIG5JdGVyID0gbnJvdyhkYXRhc2V0KQ0KICBMYW1iZGExID0gcmVwKHBhcmFtZXRlcnNbJ0xhbWJkYTEnXSwgbkl0ZXIpDQogIExhbWJkYTIgPSByZXAocGFyYW1ldGVyc1snTGFtYmRhMiddLCBuSXRlcikNCiAgDQogIGlmIChhbnkoRUNQRz49TGltaXQyKSkgTGFtYmRhMVtFQ1BHPj1MaW1pdDJdID0gMA0KICBpZiAoYW55KEVDUEc+PUxpbWl0MykpIExhbWJkYTJbRUNQRz49TGltaXQzXSA9IDANCiAgDQogIFEgPSBMYW1iZGEwICsgTGFtYmRhMS8oMS1wYXJhbWV0ZXJzWydBbHBoYTEnXSkgKyBMYW1iZGEyLygxLXBhcmFtZXRlcnNbJ0FscGhhMiddKQ0KICANCiAgQSA9IFN1bTIgKyBMYW1iZGEwKm1lYW4ucGxkICsgDQogICAgTGFtYmRhMSpWYVJBbHBoYTEvKDEtcGFyYW1ldGVyc1snQWxwaGExJ10pICsNCiAgICBMYW1iZGEyKlZhUkFscGhhMi8oMS1wYXJhbWV0ZXJzWydBbHBoYTInXSkNCiAgDQogIEVxID0gQS9RDQogIA0KICBpZiAoYW55KGlzLm5hKEVxKSkpIHJldHVybihOQSkNCiAgcmV0dXJuKEVxKQ0KfQ0KDQpgYGANCg0KV2Ugd2lsbCB1c2UgYSBsaXN0IHdpdGggYWxsIHRoZSBwYXJhbWV0ZXJzIGFuZCBkYXRhIHdoaWNoIHdpbGwgYmUgY2FsbGVkIEVDUEdfT2JqZWN0LCBpbiBvcmRlciB0byBmYWNpbGl0YXRlIHRoZSBuZXh0IGZ1bmN0aW9uIGNhbGxzLiBUaGUgbmV4dCBmdW5jdGlvbiB3aWxsIGNyZWF0ZSB0aGUgbGlzdC4NCg0KYGBge3J9DQpjcmVhdGVFQ1BHT2JqZWN0IDwtIGZ1bmN0aW9uKGRhdGFzZXQsIGRpc2NvdW50VmVjdG9yLCBmb3J3YXJkUHJpY2UsIGJ1eWVyUGFyYW0sIHNlbGxlclBhcmFtKSB7DQogIFZhUjAgPSBhcHBseShkYXRhc2V0LCAxLCBxdWFudGlsZSwgMSkNCiAgVmFSMSA9IGFwcGx5KGRhdGFzZXQsIDEsIHF1YW50aWxlLCAwKQ0KICBtZWFuUExEID0gYXBwbHkoZGF0YXNldCwgMSwgbWVhbikNCiAgDQogIEVDUEdPYmplY3QgPSBsaXN0KA0KICAgIGRhdGFzZXQ9ZGF0YXNldCwNCiAgICBkaXNjb3VudFZlY3Rvcj1kaXNjb3VudFZlY3RvciwNCiAgICBmb3J3YXJkUHJpY2U9Zm9yd2FyZFByaWNlLA0KICAgIGJ1eWVyUGFyYW09YnV5ZXJQYXJhbSwNCiAgICBzZWxsZXJQYXJhbT1zZWxsZXJQYXJhbSwNCiAgICBWYVIwPVZhUjAsDQogICAgVmFSMT1WYVIxLA0KICAgIG1lYW5QTEQ9bWVhblBMRA0KICApDQogIHJldHVybihFQ1BHT2JqZWN0KQ0KfQ0KYGBgDQoNClRoZW4gd2Ugd2lsbCBkZWZpbmUgYSBmdW5jdGlvbiB0byBvYnRhaW4gdGhlIGVxdWlsaWJyaXVtIHByaWNlIHVzaW5nIHRoZSBjYWxjdWxhdGVkIHBoaSBmb3IgZWFjaCBhZ2VudC4gVGhlIGRpc3RpbmN0aW9uIGJldHdlZW4gdGhlIGFnZW50cyBtdXN0IGJlIG1hZGUgYmVjYXVzZSB0aGUgc2lnbmFsIG9mIHRoZSBwcmljZSBkYXRhIGNoYW5nZXMgYmV0d2VlbiB0aGVtLCBiZWNhdXNlIG9mIHRoZSBDb250cmFjdCBmb3IgRGlmZmVyZW5jZXMgcmV2ZW51ZSBlcXVhdGlvbnMuIA0KDQpgYGB7cn0NCkVDUEcuZXF1aWxpYnJpdW0gPC0gZnVuY3Rpb24gKEVDUEdvYmplY3QpIHsNCiAgYXV4TGlzdCA9IGxpc3QoDQogICAgVmFSMCA9IEVDUEdvYmplY3QkVmFSMCwNCiAgICBWYVIxID0gRUNQR29iamVjdCRWYVIxLA0KICAgIG1lYW5QTEQgPSBFQ1BHb2JqZWN0JG1lYW5QTEQNCiAgKQ0KICANCiAgYnV5ZXIucGhpID0gRUNQRy5waGkoRUNQR29iamVjdCRidXllclBhcmFtLCBFQ1BHb2JqZWN0JGRhdGFzZXQsIGF1eExpc3QpDQogIHNlbGxlci5waGkgPSAtRUNQRy5waGkoRUNQR29iamVjdCRzZWxsZXJQYXJhbSwgRUNQR29iamVjdCRkYXRhc2V0LCBhdXhMaXN0LCBidXllcj1GQUxTRSkNCiAgDQogIGVxdWlsaWJyaXVtLnByaWNlcyA9IChidXllci5waGkgKyBzZWxsZXIucGhpKS8yDQogIA0KICBhZGp1c3RlZC5wcmljZXMgPSBlcXVpbGlicml1bS5wcmljZXMvRUNQR29iamVjdCRkaXNjb3VudA0KICANCiAgYXZlcmFnZVByaWNlID0gbWVhbihhZGp1c3RlZC5wcmljZXMpDQogIA0KICBFQ1BHb2JqZWN0JGJ1eWVyUGhpID0gYnV5ZXIucGhpDQogIEVDUEdvYmplY3Qkc2VsbGVyUGhpID0gc2VsbGVyLnBoaQ0KICBFQ1BHb2JqZWN0JGVxdWlsaWJyaXVtUHJpY2VzID0gZXF1aWxpYnJpdW0ucHJpY2VzDQogIEVDUEdvYmplY3QkYWRqdXN0ZWRQcmljZXM9YWRqdXN0ZWQucHJpY2VzDQogIEVDUEdvYmplY3QkYXZlcmFnZVByaWNlPWF2ZXJhZ2VQcmljZQ0KICANCiAgcmV0dXJuKEVDUEdvYmplY3QpDQp9DQpgYGANCg0KTmV4dCBmdW5jdGlvbiBpcyB1c2VkIGluIGEgcmVjdXJzaXZlIGNhbGwgdG8gY2FsY3VsYXRlIHNlbnNpYmlsaXR5Lg0KDQpgYGB7cn0NCnNlbnNpYmlsaXR5LmZvbyA8LSBmdW5jdGlvbihpLCBhcmcsIG91dHB1dCwgY29tYmluYXRpb24ubWF0cml4LCBFQ1BHb2JqZWN0KSB7DQogIA0KICBFQ1BHb2JqZWN0W1thcmddXVtUUlVFXSA9IGNvbWJpbmF0aW9uLm1hdHJpeFtpLCBdDQogIA0KICByZXR1cm4oRUNQRy5lcXVpbGlicml1bShFQ1BHb2JqZWN0KVtbb3V0cHV0XV0pDQp9DQpgYGANCg0KQW5kIGZpbmFsbHkgYSBwbG90dGluZyBmdW5jdGlvbiBmb3IgdGhlIEVDUEdfT2JqZWN0Lg0KDQpgYGB7cn0NCnNlbnNpYmlsaXR5LnByZXR0eVBsb3QgPC0gZnVuY3Rpb24ocGxvdC5tYXRyaXgsIHhsYWJlbD0iIiwgeWxpbT1jKDAsMjUwKSwgbHdkID0gMiwgbGVnZW5kVGl0bGUsIGxlZ2VuZFBvcz0iYm90dG9tcmlnaHQiLCBpbnNldD0wLjA1LCBuY29sPTEsIGNleD0xLCBsZWdDZXg9MC45KSB7DQogIE5zZXJpZXMgPSBuY29sKHBsb3QubWF0cml4KQ0KICB2aXJpZGlzLnBhbGxldHRlID0gdmlyaWRpcyhOc2VyaWVzKQ0KICBsaW5lLnR5cGVzID0gcmVwKDEsIE5zZXJpZXMpDQogIA0KICBOcm93cyA9IDE6bnJvdyhwbG90Lm1hdHJpeCkNCiAgbGFiZWxzPXJvdy5uYW1lcyhwbG90Lm1hdHJpeCkNCiAgDQogIHBhcihmYW1pbHk9IkEiLCBjZXg9Y2V4KQ0KICBtYXRwbG90KE5yb3dzLCBwbG90Lm1hdHJpeCwgdHlwZT0nbCcsIHhsYWI9eGxhYmVsLCB5bGFiPSdFbmVyZ3kgUHJpY2UgKFIkL01XaCknLCB5bGltPXlsaW0sIGx0eT1saW5lLnR5cGVzLCBjb2w9dmlyaWRpcy5wYWxsZXR0ZSwgbHdkPWx3ZCwgYXhlcz1GKQ0KICBheGlzKDIpDQogIGF4aXMoc2lkZT0xLGF0PU5yb3dzLGxhYmVscz1sYWJlbHMpDQogIGdyaWQgKE5VTEwsTlVMTCwgbHR5ID0gNiwgY29sID0gImdyZXkiKQ0KICBsZWdlbmQoeD1sZWdlbmRQb3MsIGluc2V0PWluc2V0LCBsZWdlbmQ9Y29sbmFtZXMocGxvdC5tYXRyaXgpLCBob3Jpej1GQUxTRSwgbHR5PWxpbmUudHlwZXMsIGNvbD12aXJpZGlzLnBhbGxldHRlLCBsd2Q9bHdkLCBjZXg9bGVnQ2V4LCBuY29sPW5jb2wsIHRpdGxlPWxlZ2VuZFRpdGxlKQ0KfQ0KYGBgDQoNCiMjIyBMb2NhbCB2YXJpYWJsZXMgZGVmaW5pdGlvbg0KDQpUaGVzZSB2YXJpYWJsZXMgd2lsbCBiZSB1c2VkIGZvciBhbGwgc2Vuc2liaWxpdHkgdGVzdHMuIFRoZSBvcHRpbWl6YXRpb24gcGFyYW1ldGVycyBhcmUgb2J0YWluZWQgZnJvbSB0aGUgdHJhaW5pbmcgc3RlcC4NCg0KYGBge3J9DQogDQpidXllci5wYXJhbSA9IGMoTGFtYmRhMSA9IDAuNTcxMjQxMSwgTGFtYmRhMiA9IDAuMTQwMDQ1MywgQWxwaGExID0gMC41ODQ1MzA2LCBBbHBoYTIgPSAwLjU5OTUwODcpDQpzZWxsZXIucGFyYW0gPSBjKExhbWJkYTEgPSAwLjM0ODM1NTIsIExhbWJkYTIgPSAwLjU1OTkwODEsIEFscGhhMSA9IDAuNzI1NjcyNiwgQWxwaGEyID0gMC45NzM3NjEwKQ0KYGBgDQoNCiMjIyBWYXJpYWJsZXMgZm9yIEphbnVhcnkgQSsxIGFuYWx5c2lzDQoNCkNyZWF0ZSBhIHZlY3RvciB3aXRoIGRpc2NvdW50IHJhdGVzIHRvIGFkanVzdCB0aGUgZXF1aWxpYnJpdW0gcHJpY2VzIHdpdGggdGhlIHJpc2stZnJlZSByZXR1cm4gcmF0ZS4gUmlzay1mcmVlIHJldHVybiByYXRlIGlzIG9idGFpbmVkIGFzIGFuIGFwcHJveGltYXRpb24gb2YgdGhlIFNFTElDIG5hdGlvbmFsIHJhdGUuDQoNCmBgYHtyfQ0KIyBTZXQgcmlzayBmcmVlIHJhdGVzDQphbnVhbFJhdGUgPSAwLjA1DQptb250aGx5UmF0ZSA9ICgxK2FudWFsUmF0ZSleKDEvMTIpLTENCnRlbXAuZGlzY291bnQudmVjdG9yIDwtIHJlcChOQSwgMjMpDQpmb3IgKGkgaW4gMToyMyl7DQogIHRlbXAuZGlzY291bnQudmVjdG9yW2ldIDwtICgxK21vbnRobHlSYXRlKV4oaS0xKQ0KfQ0KamFudWFyeUExLmRpc2NvdW50VmVjdG9yID0gdGVtcC5kaXNjb3VudC52ZWN0b3JbMTI6MjNdDQpgYGANCg0KSW5wdXQgdGhlIGZvcndhcmQgcHJpY2UgZm9yIGVsZWN0cmljaXR5IG9idGFpbmVkIGZyb20gRENpZGUgRW5lcmdpYS4NCg0KYGBge3J9DQpqYW51YXJ5QTEuZm9yd2FyZFByaWNlID0gMTk5LjMyDQpgYGANCg0KIyMjIExvYWRpbmcgdGhlIEphbnVhcnkgQSsxIGRhdGENCg0KTG9hZGluZyBmdXR1cmUgc3BvdCBwcmljZSAoUExEKSBkYXRhLg0KDQpgYGB7cn0NCmphbnVhcnlBMS5vcmlnaW5hbC5kYXRhID0gcmVhZC5jc3YoZmlsZT0nLi9EYXRhL0RhdGFKYW51YXJ5QTEuY3N2JywgY29sLm5hbWVzPXBhc3RlKG1vbnRoLmFiYiwgMjAyMCwgc2VwPSItIikpDQpqYW51YXJ5QTEuZGF0YSA9IHQoamFudWFyeUExLm9yaWdpbmFsLmRhdGEpDQpgYGANCg0KQ3JlYXRlIGEgbGlzdCB3aXRoIGFsbCB2YXJpYWJsZXMgYW5kIGRhdGEgdG8gcGFzcyBvbiB0byBmdW5jdGlvbnMuDQoNCmBgYHtyfQ0KamFudWFyeUExLkVDUEdfb2JqZWN0ID0gY3JlYXRlRUNQR09iamVjdChkYXRhc2V0ID0gamFudWFyeUExLmRhdGEsIGJ1eWVyUGFyYW0gPSBidXllci5wYXJhbSwgc2VsbGVyUGFyYW0gPSBzZWxsZXIucGFyYW0sIGRpc2NvdW50VmVjdG9yID0gamFudWFyeUExLmRpc2NvdW50VmVjdG9yLCBmb3J3YXJkUHJpY2UgPSBqYW51YXJ5QTEuZm9yd2FyZFByaWNlKQ0KYGBgDQoNCkdldCB0aGUgb3JpZ2luYWwgcmVzdWx0cyBmb3IgY29tcGFyaXNvbi4NCg0KYGBge3J9DQpqYW51YXJ5QTEuUmVzdWx0cyA9IEVDUEcuZXF1aWxpYnJpdW0oamFudWFyeUExLkVDUEdfb2JqZWN0KQ0KYGBgDQoNCg0KIyMjIFNlbnNpYmlsaXR5IGZvciBMYW1iZGEgMSBhbmQgTGFtYmRhIDINCg0KVGhlIGZpcnN0IGFuYWx5c2lzIGlzIHJlZ2FyZGluZyAkXGxhbWJkYV8xJCBhbmQgJFxsYW1iZGFfMiQgcGFyYW1ldGVycyBmb3IgZWFjaCBhZ2VudC4NClRob3NlIGFyZSB0aGUgYXV4aWxpYXJ5IHZhcmlhYmxlcyBzZXQgZm9yIHRoaXMgdGFzay4NCg0KYGBge3J9DQpsYW1iZGFzLnZlYyA9IHNlcSgwLCAwLjQ1LCBieT0wLjA1KQ0KbGFtYmRhcy5tYXRyaXggPSBhcy5tYXRyaXgoZXhwYW5kLmdyaWQobGFtYmRhcy52ZWMsIGxhbWJkYXMudmVjKSkNCmNvbG5hbWVzKGxhbWJkYXMubWF0cml4KSA9IGMoJ0xhbWJkYTEnLCAnTGFtYmRhMicpDQpuTGFtYmRhcyA9IG5yb3cobGFtYmRhcy5tYXRyaXgpDQpgYGANCg0KRnJvbSB0aGUgcmFuZ2Ugc2V0IGFib3ZlIGEgY29tYmluYXRpb24gbWF0cml4IGlzIGNyZWF0ZWQgd2l0aCB0aGUgKipidXllcioqIHBhcmFtZXRlcnMuDQoNCmBgYHtyfQ0KYnV5ZXIubGFtYmRhcy5tYXRyaXggPSBjYmluZChsYW1iZGFzLm1hdHJpeCwgQWxwaGExPXJlcChidXllci5wYXJhbVsnQWxwaGExJ10sIG5MYW1iZGFzKSwgQWxwaGEyPXJlcChidXllci5wYXJhbVsnQWxwaGEyJ10sIG5MYW1iZGFzKSkNCmBgYA0KDQpOZXh0LCBjYWxsIHRoZSBzZW5zaWJpbGl0eSBmdW5jdGlvbiBhbmQgY3JlYXRlIGEgbWF0cml4IG9mIHRoZSByZXN1bHRzIGZvciB0aGUgKipidXllcioqLg0KDQpgYGB7cn0NCmxhbWJkYUJ1eWVyLnNlbnNpYmlsaXR5ID0gc2FwcGx5KDE6bkxhbWJkYXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZW5zaWJpbGl0eS5mb28sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmcgPSAiYnV5ZXJQYXJhbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRwdXQgPSAiYXZlcmFnZVByaWNlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbWJpbmF0aW9uLm1hdHJpeCA9IGJ1eWVyLmxhbWJkYXMubWF0cml4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgamFudWFyeUExLkVDUEdfb2JqZWN0KQ0KDQpsYW1iZGFCdXllci5zZW5zaU1hdHJpeCA9IG1hdHJpeChsYW1iZGFCdXllci5zZW5zaWJpbGl0eSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2w9bGVuZ3RoKGxhbWJkYXMudmVjKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpbW5hbWVzID0gbGlzdChsYW1iZGFzLnZlYywgbGFtYmRhcy52ZWMpKQ0KYGBgDQoNCkRvIHRoZSBzYW1lIGZvciB0aGUgKipzZWxsZXIqKi4NCg0KYGBge3J9DQpzZWxsZXIubGFtYmRhcy5tYXRyaXggPSBjYmluZChsYW1iZGFzLm1hdHJpeCwgQWxwaGExPXJlcChzZWxsZXIucGFyYW1bJ0FscGhhMSddLCBuTGFtYmRhcyksIEFscGhhMj1yZXAoc2VsbGVyLnBhcmFtWydBbHBoYTInXSwgbkxhbWJkYXMpKQ0KYGBgDQoNCmBgYHtyfQ0KbGFtYmRhU2VsbGVyLnNlbnNpYmlsaXR5ID0gc2FwcGx5KDE6bkxhbWJkYXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2Vuc2liaWxpdHkuZm9vLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFyZyA9ICJzZWxsZXJQYXJhbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0cHV0ID0gImF2ZXJhZ2VQcmljZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21iaW5hdGlvbi5tYXRyaXggPSBzZWxsZXIubGFtYmRhcy5tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBqYW51YXJ5QTEuRUNQR19vYmplY3QpDQoNCmxhbWJkYVNlbGxlci5zZW5zaU1hdHJpeCA9IG1hdHJpeChsYW1iZGFTZWxsZXIuc2Vuc2liaWxpdHksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sPWxlbmd0aChsYW1iZGFzLnZlYyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieXJvdyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaW1uYW1lcyA9IGxpc3QobGFtYmRhcy52ZWMsIGxhbWJkYXMudmVjKSkNCmBgYA0KDQpUaGUgcmVzdWx0IGFyZSBwbG90dGVkIGluIGFuIGludGVyYWN0aXZlIHdpZGdldCB1c2luZyAqcGxvdGx5Ki4NCg0KYGBge3J9DQpyZXF1aXJlKHBsb3RseSkNCg0KYXgueHkgPC0gbGlzdCh0aWNrZXRtb2RlID0gJ2FycmF5JywgdGlja3RleHQgPSBhcy5jaGFyYWN0ZXIobGFtYmRhcy52ZWMpLCB0aWNrdmFscz0wOjkpDQpheHogPC0gbGlzdCh0aXRsZSA9ICJQb3dlciBQcmljZSAoUiQvTVdoKSIpDQpgYGANCg0KQW5hbHlzaXMgb2YgJFxsYW1iZGFfMSQgYW5kICRcbGFtYmRhXzIkIGZvciB0aGUgKipidXllcioqLg0KDQpgYGB7cn0NCmZpZy5CdXllciA8LSBwbG90X2x5KHdpZHRoPTcwMCwgaGVpZ2h0PTUwMCkgJT4lDQogIGFkZF9zdXJmYWNlKHogPSBsYW1iZGFCdXllci5zZW5zaU1hdHJpeCkgJT4lDQogIGxheW91dChzY2VuZSA9IGxpc3QoeGF4aXM9YyhheC54eSwgdGl0bGUgPSAiQnV5ZXIgTGFtYmRhMSIpLHlheGlzPWMoYXgueHksIHRpdGxlID0gIkJ1eWVyIExhbWJkYTIiKSx6YXhpcz1heHopKQ0KDQpmaWcuQnV5ZXINCmBgYA0KDQpBbmFseXNpcyBvZiAkXGxhbWJkYV8xJCBhbmQgJFxsYW1iZGFfMiQgZm9yIHRoZSAqKnNlbGxlcioqLg0KDQpgYGB7cn0NCmZpZy5TZWxsZXIgPC0gcGxvdF9seSh3aWR0aD03MDAsIGhlaWdodD01MDApICU+JQ0KICBhZGRfc3VyZmFjZSh6ID0gbGFtYmRhU2VsbGVyLnNlbnNpTWF0cml4KSAlPiUNCiAgbGF5b3V0KHNjZW5lID0gbGlzdCh4YXhpcz1jKGF4Lnh5LCB0aXRsZSA9ICJTZWxsZXIgTGFtYmRhMSIpLHlheGlzPWMoYXgueHksIHRpdGxlID0gIlNlbGxlciBMYW1iZGEyIiksemF4aXM9YXh6KSkNCg0KZmlnLlNlbGxlcg0KYGBgDQoNCiMjIyBTZW5zaWJpbGl0eSBmb3IgTGFtYmRhIDEgYW5kIEFscGhhIDEgaW4gRUNQDQoNClRoZSBmb2xsb3dpbmcgdGVzdCBpcyByZWdhcmRpbmcgYm90aCAkXGFscGhhJCBhbmQgJFxsYW1iZGEkIHBhcmFtZXRlcnMgZm9yIGVhY2ggYWdlbnQgdXNpbmcgRUNQIG1lYXN1cmUgaW5zdGVhZCBvZiBFQ1BfRy4NClRob3NlIGFyZSB0aGUgYXV4aWxpYXJ5IHZhcmlhYmxlcyBzZXQgZm9yIHRoaXMgdGFzay4NCg0KYGBge3J9DQpsYW1iZGFBbHBoYS52ZWMgPSBzZXEoMCwgMC45LCBieT0wLjEpDQpsYW1iZGFBbHBoYS5tYXRyaXggPSBhcy5tYXRyaXgoZXhwYW5kLmdyaWQobGFtYmRhQWxwaGEudmVjLCBsYW1iZGFBbHBoYS52ZWMpKQ0KY29sbmFtZXMobGFtYmRhQWxwaGEubWF0cml4KSA9IGMoJ0xhbWJkYTEnLCAnQWxwaGExJykNCm5MYW1iZGFBbHBoYSA9IG5yb3cobGFtYmRhQWxwaGEubWF0cml4KQ0KYGBgDQoNCkZyb20gdGhlIHJhbmdlIHNldCBhYm92ZSBhIGNvbWJpbmF0aW9uIG1hdHJpeCBpcyBjcmVhdGVkIHdpdGggdGhlICoqYnV5ZXIqKiBwYXJhbWV0ZXJzLg0KDQpgYGB7cn0NCmJ1eWVyLmxhbWJkYUFscGhhLm1hdHJpeCA9IGNiaW5kKGxhbWJkYUFscGhhLm1hdHJpeCwgTGFtYmRhMj1yZXAoMCwgbkxhbWJkYUFscGhhKSwgQWxwaGEyPXJlcChidXllci5wYXJhbVsnQWxwaGEyJ10sIG5MYW1iZGFBbHBoYSkpDQpidXllci5sYW1iZGFBbHBoYS5tYXRyaXggPSBidXllci5sYW1iZGFBbHBoYS5tYXRyaXhbLCBjKCdMYW1iZGExJywgJ0xhbWJkYTInLCAnQWxwaGExJywgJ0FscGhhMicpXQ0KYGBgDQoNCk5leHQsIGNhbGwgdGhlIHNlbnNpYmlsaXR5IGZ1bmN0aW9uIGFuZCBjcmVhdGUgYSBtYXRyaXggb2YgdGhlIHJlc3VsdHMgZm9yIHRoZSAqKmJ1eWVyKiouDQoNCmBgYHtyfQ0KbGFtYmRhQWxwaGFCdXllci5zZW5zaWJpbGl0eSA9IHNhcHBseSgxOm5MYW1iZGFBbHBoYSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbnNpYmlsaXR5LmZvbywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFyZz0iYnV5ZXJQYXJhbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRwdXQgPSAiYXZlcmFnZVByaWNlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbWJpbmF0aW9uLm1hdHJpeCA9IGJ1eWVyLmxhbWJkYUFscGhhLm1hdHJpeCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpdGhpbihqYW51YXJ5QTEuRUNQR19vYmplY3QsIGJ1eWVyUGFyYW1bVFJVRV0gPC0gcmVwKDAsNCkpKQ0KDQpsYW1iZGFBbHBoYUJ1eWVyLnNlbnNpTWF0cml4ID0gbWF0cml4KGxhbWJkYUFscGhhQnV5ZXIuc2Vuc2liaWxpdHksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sPWxlbmd0aChsYW1iZGFBbHBoYS52ZWMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnlyb3cgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBsaXN0KGxhbWJkYUFscGhhLnZlYywgbGFtYmRhQWxwaGEudmVjKSkNCmBgYA0KDQpBbmQgdGhlIHNhbWUgaXMgZG9uZSBmb3IgdGhlICoqc2VsbGVyKiouDQoNCmBgYHtyfQ0Kc2VsbGVyLmxhbWJkYUFscGhhLm1hdHJpeCA9IGNiaW5kKGxhbWJkYUFscGhhLm1hdHJpeCwgTGFtYmRhMj1yZXAoMCwgbkxhbWJkYUFscGhhKSwgQWxwaGEyPXJlcChzZWxsZXIucGFyYW1bJ0FscGhhMiddLCBuTGFtYmRhQWxwaGEpKQ0Kc2VsbGVyLmxhbWJkYUFscGhhLm1hdHJpeCA9IHNlbGxlci5sYW1iZGFBbHBoYS5tYXRyaXhbLCBjKCdMYW1iZGExJywgJ0xhbWJkYTInLCAnQWxwaGExJywgJ0FscGhhMicpXQ0KYGBgDQoNCmBgYHtyfQ0KbGFtYmRhQWxwaGFTZWxsZXIuc2Vuc2liaWxpdHkgPSBzYXBwbHkoMTpuTGFtYmRhQWxwaGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2Vuc2liaWxpdHkuZm9vLGFyZz0ic2VsbGVyUGFyYW0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dCA9ICJhdmVyYWdlUHJpY2UiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbWJpbmF0aW9uLm1hdHJpeCA9IHNlbGxlci5sYW1iZGFBbHBoYS5tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2l0aGluKGphbnVhcnlBMS5FQ1BHX29iamVjdCwgc2VsbGVyUGFyYW1bVFJVRV0gPC0gcmVwKDAsNCkpKQ0KDQpsYW1iZGFBbHBoYVNlbGxlci5zZW5zaU1hdHJpeCA9IG1hdHJpeChsYW1iZGFBbHBoYVNlbGxlci5zZW5zaWJpbGl0eSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sPWxlbmd0aChsYW1iZGFBbHBoYS52ZWMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaW1uYW1lcyA9IGxpc3QobGFtYmRhQWxwaGEudmVjLCBsYW1iZGFBbHBoYS52ZWMpKQ0KYGBgDQoNClRoZSByZXN1bHRzIGFyZSB2aXN1YWxpemVkIHdpdGggb3VyIG93biBwbG90IGZ1bmN0aW9uLiBMaWJyYXJ5ICp2aXJpZGlzKiBpcyBjYWxsZWQgdG8gcHJvdmlkZSBjb2xvciBwYWxsZXR0ZSBmb3IgY29sb3JibGluZCB2aXN1YWxpemF0aW9uLg0KDQpgYGB7cn0NCnJlcXVpcmUodmlyaWRpcykNCmBgYA0KDQpBbHBoYS1MYW1iZGEgc2Vuc2liaWxpdHkgZm9yIHRoZSAqKmJ1eWVyKiouDQoNCmBgYHtyfQ0Kc2Vuc2liaWxpdHkucHJldHR5UGxvdChsYW1iZGFBbHBoYUJ1eWVyLnNlbnNpTWF0cml4LCB4bGFiZWw9ZXhwcmVzc2lvbihwYXN0ZSgiQnV5ZXIgIiwgYWxwaGEsIDEpKSwgeWxpbT1jKDE3MCwgMjA1KSwgbmNvbD0yLCBjZXg9MS4yLCBsZWdDZXg9MC43LCBsd2Q9MiwgbGVnZW5kVGl0bGU9ZXhwcmVzc2lvbihwYXN0ZSgiQnV5ZXIgIiwgbGFtYmRhLCAxKSkpDQpgYGANCg0KQWxwaGEtTGFtYmRhIHNlbnNpYmlsaXR5IGZvciB0aGUgKipzZWxsZXIqKi4NCg0KYGBge3J9DQpzZW5zaWJpbGl0eS5wcmV0dHlQbG90KGxhbWJkYUFscGhhU2VsbGVyLnNlbnNpTWF0cml4LCB4bGFiZWw9ZXhwcmVzc2lvbihwYXN0ZSgiU2VsbGVyICIsIGFscGhhLCAxKSksIHlsaW09YygwLCAxODApLCBsZWdlbmRUaXRsZT1leHByZXNzaW9uKHBhc3RlKCJTZWxsZXIgIiwgbGFtYmRhLCAxKSksIGxlZ2VuZFBvcyA9ICJ0b3BsZWZ0IiwgbmNvbD01LCBpbnNldD0wLjAyLCBjZXg9MS4yLCBsd2Q9MiwgbGVnQ2V4PTAuNykNCmBgYA0KDQojIyMgU2Vuc2liaWxpdHkgZm9yIHRoZSByaXNrLWZyZWUgcmF0ZSBvZiByZXR1cm4gaW4gSmFudWFyeSBBKzIgcmVzdWx0cw0KDQpUaGlzIHNlY3Rpb24gdGVzdHMgdGhlIHJlc3VsdCBmb3IgYm90aCB0aGUgYXZlcmFnZSBwcmljZSBhbmQgdGhlIGFkanVzdGVkIHByaWNlcyBvYnRhaW5lZCBieSB0aGUgbW9kZWwgd2l0aCBkaWZmZXJlbnQgbGV2ZWxzIG9mIHRoZSByaXNrLWZyZWUgcmF0ZS4NCg0KIyMjIERlZmluZSBWYXJpYWJsZXMgZm9yIEphbnVhcnkgQSsyIHZhbGlkYXRpb24NCg0KQ3JlYXRlIGEgdmVjdG9yIHdpdGggZGlzY291bnQgcmF0ZXMgdG8gYWRqdXN0IHRoZSBlcXVpbGlicml1bSBwcmljZXMgd2l0aCB0aGUgcmlzay1mcmVlIHJldHVybiByYXRlLiBBdCB0aGlzIHBvaW50IGFsbCB2YWx1ZXMgYXJlIHNldCB0byAxLg0KDQpgYGB7cn0NCmphbnVhcnlBMi5kaXNjb3VudFZlY3RvciA9IHJlcCgxLCAxMikNCmBgYA0KDQpJbnB1dCB0aGUgZm9yd2FyZCBwcmljZSBmb3IgZWxlY3RyaWNpdHkgb2J0YWluZWQgZnJvbSBEQ2lkZSBFbmVyZ2lhLg0KDQpgYGB7cn0NCmphbnVhcnlBMi5mb3J3YXJkUHJpY2UgPSAxNzkuMjYNCmBgYA0KDQojIyMgTG9hZGluZyB0aGUgSmFudWFyeSBBKzIgZGF0YQ0KDQpMb2FkaW5nIGZ1dHVyZSBzcG90IHByaWNlIChQTEQpIGRhdGEuDQoNCmBgYHtyfQ0KamFudWFyeUEyLm9yaWdpbmFsLmRhdGEgPSByZWFkLmNzdihmaWxlPScuL0RhdGEvRGF0YUphbnVhcnlBMi5jc3YnLCBjb2wubmFtZXM9cGFzdGUobW9udGguYWJiLCAyMDIxLCBzZXA9Ii0iKSkNCmphbnVhcnlBMi5kYXRhID0gdChqYW51YXJ5QTIub3JpZ2luYWwuZGF0YSkNCmBgYA0KDQpDcmVhdGUgYSBsaXN0IHdpdGggYWxsIHZhcmlhYmxlcyBhbmQgZGF0YSB0byBwYXNzIG9uIHRvIGZ1bmN0aW9ucy4NCg0KYGBge3J9DQpqYW51YXJ5QTIuRUNQR19vYmplY3QgPSBjcmVhdGVFQ1BHT2JqZWN0KGRhdGFzZXQgPSBqYW51YXJ5QTIuZGF0YSwgYnV5ZXJQYXJhbSA9IGJ1eWVyLnBhcmFtLCBzZWxsZXJQYXJhbSA9IHNlbGxlci5wYXJhbSwgZGlzY291bnRWZWN0b3IgPSBqYW51YXJ5QTIuZGlzY291bnRWZWN0b3IsIGZvcndhcmRQcmljZSA9IGphbnVhcnlBMi5mb3J3YXJkUHJpY2UpDQpgYGANCg0KIyMjIFByZXBhcmUgdmFyaWFibGVzIGZvciB0aGUgc2Vuc2liaWxpdHkgYW5hbHlzaXMNCg0KU2V0IGF1eGlsaWFyeSB2YXJpYWJsZXMuDQoNCmBgYHtyfQ0KYW5udWFsUmF0ZXMudmVjID0gc2VxKDAsIDAuMDksIGJ5PTAuMDEpDQptb250aGx5UmF0ZXMudmVjID0gKDErYW5udWFsUmF0ZXMudmVjKV4oMS8xMiktMQ0KblJhdGVzID0gbGVuZ3RoKG1vbnRobHlSYXRlcy52ZWMpDQpgYGANCg0KQ3JlYXRlIGEgZGlzY291bnQgbWF0cml4IHRvIGJlIHBhc3NlZCB0byB0aGUgc2Vuc2liaWxpdHkgZnVuY3Rpb24uDQoNCmBgYHtyfQ0KZGlzY291bnQubWF0cml4IDwtIG1hdHJpeChOQSwgbmNvbD0zNSwgbnJvdz1uUmF0ZXMpDQpmb3IgKGlpIGluIDE6blJhdGVzKSB7DQogIGZvciAoaSBpbiAxOjM1KXsNCiAgICBkaXNjb3VudC5tYXRyaXhbaWksIGldIDwtICgxK21vbnRobHlSYXRlcy52ZWNbaWldKV4oaS0xKQ0KICB9DQp9DQpkaXNjb3VudC5tYXRyaXggPSBkaXNjb3VudC5tYXRyaXhbLCAyNDozNV0NCnJvd25hbWVzKGRpc2NvdW50Lm1hdHJpeCkgPSBhbm51YWxSYXRlcy52ZWMNCmBgYA0KDQpBcHBseSB0aGUgc2Vuc2liaWxpdHkgZnVuY3Rpb24gdG8gb2J0YWluIHRoZSBhdmVyYWdlIHByaWNlIGZvciBlYWNoIHJpc2stZnJlZSByYXRlLg0KDQpgYGB7cn0NCnJhdGVzLnNlbnNpYmlsaXR5ID0gc2FwcGx5KDE6blJhdGVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbnNpYmlsaXR5LmZvbywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmc9ImRpc2NvdW50VmVjdG9yIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dCA9ICJhdmVyYWdlUHJpY2UiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21iaW5hdGlvbi5tYXRyaXggPSBkaXNjb3VudC5tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgamFudWFyeUEyLkVDUEdfb2JqZWN0KQ0KbmFtZXMocmF0ZXMuc2Vuc2liaWxpdHkpID0gYW5udWFsUmF0ZXMudmVjDQpgYGANCg0KUGxvdCB0aGUgcmVzdWx0cw0KDQpgYGB7cn0NCnBsb3QoYW5udWFsUmF0ZXMudmVjLCByYXRlcy5zZW5zaWJpbGl0eSwgeWxpbT1jKDAsMjAwKSwgeWxhYj0iRW5lcmd5IFByaWNlIChSJC9NV2gpIiwgeGxhYj0iQW5udWFsIFJpc2stRnJlZSBSYXRlIiwgY29sPSJzdGVlbGJsdWUiKQ0KYGBgDQoNCkFwcGx5IHRoZSBzZW5zaWJpbGl0eSBmdW5jdGlvbiB0byBvYnRhaW4gdGhlIGFkanVzdGVkIHByaWNlcyBmb3IgZWFjaCByaXNrLWZyZWUgcmF0ZSBhbmQgYXJyYW5nZSB0aGUgcmVzdWx0cyBpbiBhIG1hdHJpeC4NCg0KYGBge3J9DQpyYXRlcy5hZGp1c3RlZFByaWNlcyA9IHNhcHBseSgxOm5SYXRlcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbnNpYmlsaXR5LmZvbywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFyZz0iZGlzY291bnRWZWN0b3IiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0cHV0ID0gImFkanVzdGVkUHJpY2VzIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBjb21iaW5hdGlvbi5tYXRyaXggPSBkaXNjb3VudC5tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBqYW51YXJ5QTIuRUNQR19vYmplY3QpDQoNCnJhdGVzLnNlbnNpYmlsaXR5Lm1hdHJpeCA9IG1hdHJpeChyYXRlcy5hZGp1c3RlZFByaWNlcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gblJhdGVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBsaXN0KHJvd25hbWVzKGphbnVhcnlBMi5FQ1BHX29iamVjdCRkYXRhc2V0KSwgYW5udWFsUmF0ZXMudmVjKSkNCmBgYA0KDQpQbG90IHRoZSByZXN1bHRzDQoNCmBgYHtyfQ0Kc2Vuc2liaWxpdHkucHJldHR5UGxvdChyYXRlcy5zZW5zaWJpbGl0eS5tYXRyaXgsIHlsaW09YygwLCAyMDUpLCBuY29sPTUsIGNleD0xLjIsIGxlZ0NleD0wLjcsIGx3ZD0yLCBsZWdlbmRUaXRsZT0iUmlzay1GcmVlIFJhdGUgYWEuIikNCmBgYA0KDQpDaGVjayBpZiByZXN1bHRzIGFyZSBpbnRlcm5hbGx5IGNvbnNpc3RlbnQuDQoNCmBgYHtyfQ0KamFudWFyeUEyLlJlc3VsdHMgPSBFQ1BHLmVxdWlsaWJyaXVtKHdpdGhpbihqYW51YXJ5QTIuRUNQR19vYmplY3QsIGRpc2NvdW50VmVjdG9yIDwtIGRpc2NvdW50Lm1hdHJpeFsiMC4wNSIsIF0pKQ0KamFudWFyeUEyLlJlc3VsdHMkYXZlcmFnZVByaWNlID09IHJhdGVzLnNlbnNpYmlsaXR5WyIwLjA1Il0NCmBgYA0KDQojIyMgU2VlIGFsc286DQoNCk1vZGVsIFRyYWluaW5nIChbbGlua10oLi9Nb2RlbFRyYWluaW5nTm90ZWJvb2submIuaHRtbCkpDQoNCk1vZGVsIFZhbGlkYXRpb24gKFtsaW5rXSguL01vZGVsVmFsaWRhdGlvbk5vdGVib29rLm5iLmh0bWwpKQ0K